use std::{
    env,
    path::{Path, PathBuf},
    process::{Command, Stdio},
};

fn main() {
    if let Err(e) = try_main() {
        eprintln!("{}", e);
        std::process::exit(-1);
    }
}

fn try_main() -> Result<(), Box<dyn std::error::Error>> {
    let task = env::args().nth(1);
    match task.as_deref() {
        Some("bootstrap") => bootstrap()?,
        _ => print_help(),
    }
    Ok(())
}

fn print_help() {
    eprintln!("Tasks:");
    eprintln!("bootstrap    Generate PEG parser code from grammar");
}

fn bootstrap() -> Result<(), Box<dyn std::error::Error>> {
    let src1 = generate_grammar()?;
    std::fs::rename(
        project_root().join("peg-macros/grammar.rs"),
        project_root().join("peg-macros/grammar_old.rs"),
    )?;
    std::fs::write(project_root().join("peg-macros/grammar.rs"), &src1)?;

    match generate_grammar() {
        Ok(src2) if src1 == src2 => {
            eprintln!("Success");
            return Ok(());
        }
        Ok(_) => {
            eprintln!("Generated grammar did not converge to a fixed point; restoring old grammar");
        }
        Err(_) => {
            eprintln!("Error building with updated grammar; restoring old grammar");
        }
    }

    std::fs::rename(
        project_root().join("peg-macros/grammar.rs"),
        project_root().join("peg-macros/grammar_new.rs"),
    )?;

    std::fs::rename(
        project_root().join("peg-macros/grammar_old.rs"),
        project_root().join("peg-macros/grammar.rs"),
    )?;

    Err("Failed to bootstrap grammar")?
}

fn generate_grammar() -> Result<String, Box<dyn std::error::Error>> {
    let cargo = env::var("CARGO").unwrap_or_else(|_| "cargo".to_string());

    let output = Command::new(cargo)
        .current_dir(project_root())
        .args(&[
            "run",
            "-p",
            "peg-macros",
            "--",
            "peg-macros/grammar.rustpeg",
        ])
        .stderr(Stdio::inherit())
        .output()?;

    if !output.status.success() {
        Err("failed to generate grammar")?;
    }

    let source = std::str::from_utf8(&output.stdout)?;
    let source = prettyplease::unparse(&syn::parse_file(source)?);

    Ok(String::from("// Generated by rust-peg. Do not edit.\n#[rustfmt::skip]\n") + &source)
}

fn project_root() -> PathBuf {
    Path::new(&env!("CARGO_MANIFEST_DIR"))
        .ancestors()
        .nth(1)
        .unwrap()
        .to_path_buf()
}
